from collections import OrderedDict
from stringtemplate3.templates import StringTemplate

ID = 'id'
__author__ = 'romilly'

class ClassModel(object):
    def __init__(self, modelledClass, fieldModels):
        self._modelledClass = modelledClass
        self._fieldModels = OrderedDict()
        for fieldModel in fieldModels:
            self._fieldModels[fieldModel.name()] = fieldModel

    def modelledClass(self):
        return self._modelledClass

    def fieldModels(self):
        return self._fieldModels.values()

    def initializeInstanceFields(self, instance):
        for fieldModel in self.fieldModels():
            instance.__dict__[fieldModel.name()] = None

    def insertFieldValues(self, dict, instance):
        for key in dict.keys():
            instance.__dict__[key] = dict[key]

    def createInstance(self):
        instance = object.__new__(self._modelledClass)
        return instance

    def buildInstance(self, dict):
        instance = self.createInstance()
        self.initializeInstanceFields(instance)
        self.insertFieldValues(dict, instance)
        return instance

class FieldModel(object):
    def __init__(self, name):
        self._name = name

    def name(self):
        return self._name

class TableModel(object):
    def __init__(self, name, columnModels):
        self._name = name
        self.populateColumnModelDictionary(columnModels)

    def populateColumnModelDictionary(self, columnModels):
        self._columnModels = OrderedDict()
        for columnModel in columnModels:
            self._columnModels[columnModel.name()] = columnModel

    def name(self):
        return self._name

    def columnModels(self):
        return self._columnModels.values()

    def columnSpecification(self, columnModel):
        return columnModel.columnSpecification()

    def columnSpecifications(self):
        return map(self.columnSpecification, self.columnModels())

    def createTableStatement(self):
        query = StringTemplate('CREATE TABLE $table$($columnSpecs; separator=", "$)')
        query["table"] = self.name()
        query["columnSpecs"] = self.columnSpecifications()
        return str(query)

    def columnNames(self):
        return list([col.name() for col in self.columnModels()])

class Map(object):
    def __init__(self, classModel, tableModel, fieldToColumnMaps):
        self._classModel = classModel
        self._tableModel = tableModel
        self._fieldToColumnMaps = fieldToColumnMaps

    def fieldToColumnMaps(self):
        return self._fieldToColumnMaps

    def classModel(self):
        return self._classModel

    def tableModel(self):
        return self._tableModel

    def modelledClass(self):
        return self.classModel().modelledClass()

    def sqlToSelectById(self):
        return self.sqlToSelectByColumnName('rowid')

    def tableName(self):
        return self.tableModel().name()

    def mappedColumnNames(self):
        return [map.columnName() for map in self.fieldToColumnMaps()]

    def mappedFieldNames(self):
        return [map.fieldName() for map in self.fieldToColumnMaps()]

    def sqlToSelectByColumnName(self, searchColumn):
        query = StringTemplate('SELECT $columnNames; separator=", "$ FROM $table$ WHERE $searchColumn$=?')
        query['columnNames'] = self.mappedColumnNames()
        query['table'] = self.tableName()
        query['searchColumn'] = searchColumn
        return str(query)

    def makeRowDict(self, row):
        rowDict = {}
        for (name, value) in zip(self.mappedFieldNames(), row[0]):
            rowDict[name] = value
        return rowDict

    def buildInstanceFromRow(self, row):
        return self.classModel().buildInstance(self.makeRowDict(row))

    def findById(self, database, id):
        row = database.executeQuery(self.sqlToSelectById(), (id,))
        if len(row) != 1:
            return None
        return self.buildInstanceFromRow(row)

    def instanceToTupleWithId(self, objectToSave):
        oList = list(self.instanceToTuple(objectToSave))
        oList.append(self.getId(objectToSave))
        return tuple(oList)

    def save(self, database, objectToSave):
        if self.getIdSafely(objectToSave) is None:
            database.executeQuery(self.sqlToInsert(), self.instanceToTuple(objectToSave))
            self.setId(objectToSave, database.getNewId())
        else:
            database.executeQuery(self.sqlToUpdate(), self.instanceToTupleWithId(objectToSave))

    def sqlToInsert(self):
        query = StringTemplate('INSERT INTO $table$ ($columnNames; separator=", "$) VALUES($parameters; separator = ","$)')
        query['table'] = self.tableName()
        columnNames = self.mappedColumnNames()
        query['columnNames'] = columnNames
        query['parameters'] = ['?']*len(columnNames)
        return str(query)

    def instanceToTuple(self, objectToSave):
        return tuple([objectToSave.__dict__[ftcMap.fieldName()] for ftcMap in self.fieldToColumnMaps()])

    def setId(self, objectToSave, id):
        objectToSave.__dict__[ID] = id

    def sqlToUpdate(self):
        query = StringTemplate('UPDATE $table$ SET $columnNames; separator="=?, "$=? WHERE rowid=?')
        query['table'] = self.tableName()
        columnNames = self.mappedColumnNames()
        query['columnNames'] = columnNames
        query['queryParameters'] = ['?']*len(columnNames)
        return str(query)

    def getId(self, objectToSave):
        return objectToSave.__dict__[ID]

    def getIdSafely(self, objectToSave):
        if 'id' in objectToSave.__dict__:
            return self.getId(objectToSave)
        return None

class FieldToColumnMap:
    def __init__(self, fieldName, columnName):
        self._fieldName = fieldName
        self._columnName = columnName

    def columnName(self):
        return self._columnName

    def fieldName(self):
        return self._fieldName

    def convertColumnToField(self, columnValue):
        return columnValue

    def convertFieldToColumn(self, fieldValue):
        return fieldValue

class Pom(object):
    def __init__(self, database):
        self._database = database
        self._maps = {}

    def database(self):
        return self._database

    def addMap(self, map):
        self._maps[map.modelledClass()] = map

    def mapForClass(self, objectClass):
        return self._maps[objectClass]

    def mapForObject(self, objectToSave):
        return self.mapForClass(objectToSave.__class__)

    def save(self, objectToSave):
        self.mapForObject(objectToSave).save(self.database(), objectToSave)

    def findById(self, modelledClass, id):
        return self.mapForClass(modelledClass).findById(self.database(), id)